﻿using CSharpSkin.ScrollBar.Constant;
using CSharpSkin.ScrollBar.Enum;
using CSharpSkin.ScrollBar.Event;
using CSharpSkin.ScrollBar.Struct;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

namespace CSharpSkin.ScrollBar.Class
{
    #region 滚动条基础窗口
    /// <summary>
    /// 滚动条基础窗口
    /// </summary>
    public class ScrollBarNativeWindow : NativeWindow, IDisposable
    {
        private bool _isOpenPainting;
        private System.Windows.Forms.ScrollBar _scrollBar;
        private ScrollBarNativeWindowBase _scrollBarNativeWindowBase;
        private ScrollBarMouseDownState _barMouseDownState;
        private bool _disposed;
        private const int VK_LBUTTON = 0x1;
        private const int VK_RBUTTON = 0x2;

        #region 构造
        /// <summary>
        /// 构造
        ///</summary>
        /// <param name="scrollBar"></param>
        public ScrollBarNativeWindow(System.Windows.Forms.ScrollBar scrollBar): base()
        {
            _scrollBar = scrollBar;
            CreateHandle();
        }

        ~ScrollBarNativeWindow()
        {
            this.Dispose();
        }
        #endregion

        #region 返回滚动条句柄
        /// <summary>
        /// 返回滚动条句柄
        /// </summary>
        public IntPtr ScrollBarHanlde
        {
            get { return _scrollBar.Handle; }
        }
        #endregion

        #region 返回方向
        /// <summary>
        /// 返回方向
        /// </summary>
        private Orientation Direction
        {
            get
            {
                if (_scrollBar is HScrollBar)
                {
                    return Orientation.Horizontal;
                }
                return Orientation.Vertical;
            }
        }
        #endregion

        #region 获取水平滚动条箭头位图的宽度
        /// <summary>
        /// 获取水平滚动条箭头位图的宽度
        /// </summary>
        private int HorizontalScrollBarArrowWidth
        {
            get { return SystemInformation.HorizontalScrollBarArrowWidth; }
        }
        #endregion

        #region 获取垂直滚动条箭头位图的宽度
        /// <summary>
        /// 获取垂直滚动条箭头位图的宽度
        /// </summary>
        private int VerticalScrollBarArrowWidth
        {
            get { return SystemInformation.VerticalScrollBarArrowHeight; }
        }
        #endregion

        #region 监听系统并进行重绘
        protected override void WndProc(ref Message m)
        {
            try
            {
                switch (m.Msg)
                {
                    case WINDOWMESSAGE.WM_PAINT:
                        if (!_isOpenPainting)
                        {
                            PAINTSTRUCT ps = new PAINTSTRUCT();
                            _isOpenPainting = true;
                            CSharpWinapi.BeginPaint(m.HWnd, ref ps);
                            DrawScrollBar(m.HWnd, _scrollBarNativeWindowBase.Handle);
                            CSharpWinapi.ValidateRect(m.HWnd, ref ps.rcPaint);
                            CSharpWinapi.EndPaint(m.HWnd, ref ps);
                            _isOpenPainting = false;
                            m.Result = WMRESULT.TRUE;
                        }
                        else
                        {
                            base.WndProc(ref m);
                        }
                        break;
                    case SBM.SBM_SETSCROLLINFO:
                        DrawScrollBar(m.HWnd, _scrollBarNativeWindowBase.Handle, true, false);
                        base.WndProc(ref m);
                        break;
                    case WINDOWMESSAGE.WM_STYLECHANGED:
                        DrawScrollBar(m.HWnd, _scrollBarNativeWindowBase.Handle, false, true);
                        base.WndProc(ref m);
                        break;
                    case WINDOWMESSAGE.WM_LBUTTONDOWN:
                        _barMouseDownState = GetScrollBarMouseState(m.HWnd);
                        DrawScrollBar(m.HWnd, _scrollBarNativeWindowBase.Handle);
                        base.WndProc(ref m);
                        break;
                    case WINDOWMESSAGE.WM_LBUTTONUP:
                    case WINDOWMESSAGE.WM_MOUSEMOVE:
                        DrawScrollBar(m.HWnd, _scrollBarNativeWindowBase.Handle);
                        base.WndProc(ref m);
                        break;
                    case WINDOWMESSAGE.WM_MOUSELEAVE:
                        DrawScrollBar(m.HWnd, _scrollBarNativeWindowBase.Handle);
                        base.WndProc(ref m);
                        break;
                    case WINDOWMESSAGE.WM_WINDOWPOSCHANGED:
                        WINDOWPOS pos = (WINDOWPOS)Marshal.PtrToStructure(
                            m.LParam, typeof(WINDOWPOS));
                        bool hide = (pos.flags & WP.WP_HIDEWINDOW) != 0;
                        bool show = (pos.flags & WP.WP_SHOWWINDOW) != 0;
                        if (hide)
                        {
                            _scrollBarNativeWindowBase.SetVisibale(false);
                        }
                        else if (show)
                        {
                            _scrollBarNativeWindowBase.SetVisibale(true);
                        }
                        _scrollBarNativeWindowBase.CheckBounds(m.HWnd);
                        base.WndProc(ref m);
                        break;
                    default:
                        base.WndProc(ref m);
                        break;
                }
            }
            catch
            {
            }
        }

        #endregion

        #region 绘制滚动条
        public void DrawScrollBar(IntPtr scrollBarHWnd, IntPtr maskHWnd)
        {
            DrawScrollBar(scrollBarHWnd, maskHWnd, false, false);
        }

        public void DrawScrollBar(IntPtr scrollBarHWnd, IntPtr maskHWnd,bool sbm, bool styleChanged)
        {
            Rectangle bounds;
            Rectangle trackRect;
            Rectangle topLeftArrowRect;
            Rectangle bottomRightArrowRect;
            Rectangle thumbRect;
            ScrollBarState topLeftArrowState;
            ScrollBarState bottomRightArrowState;
            ScrollBarState thumbState;
            Orientation direction = Direction;
            bool bHorizontal = direction == Orientation.Horizontal;
            ScrollBarMouseDownState  scrollBarMouseDownState;
            CalculateRect(scrollBarHWnd, bHorizontal, out bounds, out trackRect,out topLeftArrowRect, out bottomRightArrowRect, out thumbRect);
            GetState(scrollBarHWnd, bHorizontal, out scrollBarMouseDownState, out topLeftArrowState,out bottomRightArrowState, out thumbState);
            if (sbm)
            {
                if (scrollBarMouseDownState == ScrollBarMouseDownState.None)
                {
                    thumbState = ScrollBarState.Pressed;
                }
                else if (scrollBarMouseDownState == ScrollBarMouseDownState.Track)
                {
                    thumbState = ScrollBarState.Normal;
                }
            }

            if (styleChanged)
            {
                thumbState = ScrollBarState.Normal;
            }
            DrawScrollBar(maskHWnd, bounds, trackRect, topLeftArrowRect, bottomRightArrowRect,thumbRect, topLeftArrowState, bottomRightArrowState, thumbState, direction);
        }

        private void DrawScrollBar(ScrollBarState topLeftArrowState,ScrollBarState bottomRightArrowState,ScrollBarState thumbState)
        {
            Rectangle bounds;
            Rectangle trackRect;
            Rectangle topLeftArrowRect;
            Rectangle bottomRightArrowRect;
            Rectangle thumbRect;
            Orientation direction = Direction;
            bool bHorizontal = direction == Orientation.Horizontal;
            CalculateRect(ScrollBarHanlde, bHorizontal, out bounds, out trackRect,out topLeftArrowRect, out bottomRightArrowRect, out thumbRect);
            DrawScrollBar(_scrollBarNativeWindowBase.Handle, bounds, trackRect, topLeftArrowRect,bottomRightArrowRect, thumbRect, topLeftArrowState,bottomRightArrowState, thumbState, direction);
        }

        private void DrawScrollBar(IntPtr maskHWnd,Rectangle bounds,Rectangle trackRect, Rectangle topLeftArrowRect,Rectangle bottomRightArrowRect,Rectangle thumbRect,ScrollBarState topLeftArrowState,ScrollBarState bottomRightArrowState,ScrollBarState thumbState,Orientation direction)
        {
            bool bHorizontal = direction == Orientation.Horizontal;
            ArrowDirection arrowDirection;
            bool bEnabled = _scrollBar.Enabled;
            IOnScrollBarPaint paint = _scrollBar as IOnScrollBarPaint;
            if (paint == null)
            {
                return;
            }
            ImageDc tempDc = new ImageDc(bounds.Width, bounds.Height);
            IntPtr hdc = CSharpWinapi.GetDC(maskHWnd);
            try
            {
                using (Graphics g = Graphics.FromHdc(tempDc.IntPtr))
                {
                    using (PaintScrollBarTrackEventArgs te =new PaintScrollBarTrackEventArgs(g,trackRect,direction,bEnabled))
                    {
                        paint.OnPaintScrollBarTrack(te);
                    }
                    arrowDirection = bHorizontal ?ArrowDirection.Left : ArrowDirection.Up;
                    using (PaintScrollBarArrowEventArgs te =new PaintScrollBarArrowEventArgs(g,topLeftArrowRect,topLeftArrowState,arrowDirection,direction,bEnabled))
                    {
                        paint.OnPaintScrollBarArrow(te);
                    }
                    arrowDirection = bHorizontal ?ArrowDirection.Right : ArrowDirection.Down;
                    using (PaintScrollBarArrowEventArgs te =new PaintScrollBarArrowEventArgs(g,bottomRightArrowRect,bottomRightArrowState,arrowDirection,direction,bEnabled))
                    {
                        paint.OnPaintScrollBarArrow(te);
                    }
                    using (PaintScrollBarEventArgs te =new PaintScrollBarEventArgs(g,thumbRect,thumbState,direction,bEnabled))
                    {
                        paint.OnPaintScrollBar(te);
                    }
                }
                CSharpWinapi.BitBlt(hdc,0,0,bounds.Width,bounds.Height,tempDc.IntPtr,0,0,PAINTOPERATIONS.SRCCOPY);
            }
            finally
            {
                CSharpWinapi.ReleaseDC(maskHWnd, hdc);
                tempDc.Dispose();
            }
        }

        private void CalculateRect(IntPtr scrollBarHWnd,bool bHorizontal,out Rectangle bounds,out Rectangle trackRect,out Rectangle topLeftArrowRect,out Rectangle bottomRightArrowRect,out Rectangle thumbRect)
        {
            RECT rect = new RECT();
            CSharpWinapi.GetWindowRect(scrollBarHWnd, ref rect);
            CSharpWinapi.OffsetRect(ref rect, -rect.Left, -rect.Top);
            int arrowWidth = bHorizontal ? HorizontalScrollBarArrowWidth : VerticalScrollBarArrowWidth;
            bounds = rect.Rect;
            Point point = GetScrollBar(bounds, bHorizontal, arrowWidth);
            trackRect = bounds;
            if (bHorizontal)
            {
                topLeftArrowRect = new Rectangle(0, 0, arrowWidth, bounds.Height);
                bottomRightArrowRect = new Rectangle(
                    bounds.Width - arrowWidth, 0, arrowWidth, bounds.Height);
                if (_scrollBar.RightToLeft == RightToLeft.Yes)
                {
                    thumbRect = new Rectangle(
                        point.Y,
                        0,
                        point.X - point.Y,
                        bounds.Height);
                }
                else
                {
                    thumbRect = new Rectangle(
                        point.X,
                        0,
                        point.Y - point.X,
                        bounds.Height);
                }
            }
            else
            {
                topLeftArrowRect = new Rectangle(
                    0, 0, bounds.Width, arrowWidth);
                bottomRightArrowRect = new Rectangle(
                    0, bounds.Height - arrowWidth, bounds.Width, arrowWidth);
                thumbRect = new Rectangle(
                    0, point.X, bounds.Width, point.Y - point.X);
            }
        }
        #endregion

        #region 是否按下鼠标左键
        /// <summary>
        /// 是否按下鼠标左键
        /// </summary>
        /// <returns></returns>
        private bool IsMouseLeftKeyPressed()
        {
            if (SystemInformation.MouseButtonsSwapped)
            {
                return (CSharpWinapi.GetKeyState(VK_RBUTTON) < 0);
            }
            else
            {
                return (CSharpWinapi.GetKeyState(VK_LBUTTON) < 0);
            }
        }
        #endregion

        #region
        private void GetState(IntPtr scrollBarHWnd,bool bHorizontal,out ScrollBarMouseDownState scrollBarMouseDownState,out ScrollBarState topLeftArrowState,out ScrollBarState bottomRightArrowState,out ScrollBarState thumbState)
        {
            scrollBarMouseDownState = GetScrollBarMouseState(scrollBarHWnd);
            bool bLButtonDown = IsMouseLeftKeyPressed();
            bool bEnabled = _scrollBar.Enabled;
            topLeftArrowState = ScrollBarState.Normal;
            bottomRightArrowState = ScrollBarState.Normal;
            thumbState = ScrollBarState.Normal;
            switch (scrollBarMouseDownState)
            {
                case ScrollBarMouseDownState.LeftArrow:
                case ScrollBarMouseDownState.TopArrow:
                    if (bEnabled)
                    {
                        topLeftArrowState = bLButtonDown ?ScrollBarState.Pressed : ScrollBarState.Hover;
                    }
                    break;
                case ScrollBarMouseDownState.RightArrow:
                case ScrollBarMouseDownState.BottomArrow:
                    if (bEnabled)
                    {
                        bottomRightArrowState = bLButtonDown ?ScrollBarState.Pressed : ScrollBarState.Hover;
                    }
                    break;
                case ScrollBarMouseDownState.Thumb:
                    if (bEnabled)
                    {
                        thumbState = bLButtonDown ?ScrollBarState.Pressed : ScrollBarState.Hover;
                    }
                    break;
            }
        }

        #endregion


        protected void CreateHandle()
        {
            base.AssignHandle(ScrollBarHanlde);
            _scrollBarNativeWindowBase = new ScrollBarNativeWindowBase(this);
            _scrollBarNativeWindowBase.OnCreateHandle();
        }

        internal void ReleaseHandle()
        {
            if (base.Handle != IntPtr.Zero)
            {
                base.ReleaseHandle();
            }
        }

        private SCROLLBARINFO GetScrollBarInfo(IntPtr hWnd)
        {
            SCROLLBARINFO sbi = new SCROLLBARINFO();
            sbi.cbSize = Marshal.SizeOf(sbi);
            CSharpWinapi.SendMessage(hWnd,SBM.SBM_GETSCROLLBARINFO,0,ref sbi);
            return sbi;
        }

        private SCROLLBARINFO GetScrollBarInfo(IntPtr hWnd, uint objid)
        {
            SCROLLBARINFO sbi = new SCROLLBARINFO();
            sbi.cbSize = Marshal.SizeOf(sbi);
            CSharpWinapi.GetScrollBarInfo(hWnd, objid, ref sbi);
            return sbi;
        }

        private Point GetScrollBar()
        {
            bool bHorizontal = Direction == Orientation.Horizontal;
            int arrowWidth = bHorizontal ? HorizontalScrollBarArrowWidth : VerticalScrollBarArrowWidth;
            return GetScrollBar(_scrollBar.ClientRectangle,bHorizontal,arrowWidth);
        }

        private Point GetScrollBar(Rectangle rect, bool bHorizontal, int arrowWidth)
        {
            System.Windows.Forms.ScrollBar scrollBar = _scrollBar;
            int width;
            Point point = new Point();
            if (bHorizontal)
            {
                width = rect.Width - arrowWidth * 2;
            }
            else
            {
                width = rect.Height - arrowWidth * 2;
            }
            int value = scrollBar.Maximum - scrollBar.Minimum - scrollBar.LargeChange + 1;
            float thumbWidth = (float)width / ((float)value / scrollBar.LargeChange + 1);
            if (thumbWidth < 8)
            {
                thumbWidth = 8f;
            }
            if (value != 0)
            {
                int curValue = scrollBar.Value - scrollBar.Minimum;
                if (curValue > value)
                {
                    curValue = value;
                }
                point.X = (int)(curValue * ((float)(width - thumbWidth) / value));
            }
            point.X += arrowWidth;
            point.Y = point.X + (int)Math.Ceiling(thumbWidth);
            if (bHorizontal && scrollBar.RightToLeft == RightToLeft.Yes)
            {
                point.X = scrollBar.Width - point.X;
                point.Y = scrollBar.Width - point.Y;
            }
            return point;
        }

        private ScrollBarMouseDownState GetScrollBarMouseState(IntPtr hWnd)
        {
            Point point = new Point();
            RECT rect = new RECT();
            Point thumbPoint = GetScrollBar();
            int horizontalScrollBarArrowWidth =HorizontalScrollBarArrowWidth ;
            int verticalScrollBarArrowWidth = VerticalScrollBarArrowWidth;
            CSharpWinapi.GetWindowRect(hWnd, ref rect);
            CSharpWinapi.OffsetRect(ref rect, -rect.Left, -rect.Top);
            RECT tp = rect;
            CSharpWinapi.GetCursorPos(ref point);
            CSharpWinapi.ScreenToClient(hWnd, ref point);
            if (Direction == Orientation.Horizontal)
            {
                if (CSharpWinapi.PtInRect(ref rect, point))
                {
                    tp.Right = horizontalScrollBarArrowWidth;
                    if (CSharpWinapi.PtInRect(ref tp, point))
                    {
                        return ScrollBarMouseDownState.LeftArrow;
                    }
                    tp.Left = rect.Right - horizontalScrollBarArrowWidth;
                    tp.Right = rect.Right;
                    if (CSharpWinapi.PtInRect(ref tp, point))
                    {
                        return ScrollBarMouseDownState.RightArrow;
                    }
                    if (_scrollBar.RightToLeft == RightToLeft.Yes)
                    {
                        tp.Left = point.Y;
                        tp.Right = point.X;
                    }
                    else
                    {
                        tp.Left = thumbPoint.X;
                        tp.Right = thumbPoint.Y;
                    }
                    if (CSharpWinapi.PtInRect(ref tp, point))
                    {
                        return ScrollBarMouseDownState.Thumb;
                    }
                    return ScrollBarMouseDownState.Track;
                }
            }
            else
            {
                if (CSharpWinapi.PtInRect(ref rect, point))
                {
                    tp.Bottom = verticalScrollBarArrowWidth;
                    if (CSharpWinapi.PtInRect(ref tp, point))
                    {
                        return ScrollBarMouseDownState.TopArrow;
                    }
                    tp.Top = rect.Bottom - verticalScrollBarArrowWidth;
                    tp.Bottom = rect.Bottom;
                    if (CSharpWinapi.PtInRect(ref tp, point))
                    {
                        return ScrollBarMouseDownState.BottomArrow;
                    }
                    tp.Top = thumbPoint.X;
                    tp.Bottom = thumbPoint.Y;
                    if (CSharpWinapi.PtInRect(ref tp, point))
                    {
                        return ScrollBarMouseDownState.Thumb;
                    }
                    return ScrollBarMouseDownState.Track;
                }
            }
            return ScrollBarMouseDownState.None;
        }

        private void InvalidateWindow(bool messaged)
        {
            InvalidateWindow(ScrollBarHanlde, messaged);
        }

        private void InvalidateWindow(IntPtr hWnd, bool messaged)
        {
            if (messaged)
            {
                CSharpWinapi.RedrawWindow(hWnd,IntPtr.Zero,IntPtr.Zero,RDW.RDW_INTERNALPAINT);
            }
            else
            {
                CSharpWinapi.RedrawWindow(hWnd,IntPtr.Zero,IntPtr.Zero,RDW.RDW_INVALIDATE | RDW.RDW_UPDATENOW);
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {
                    if (_scrollBarNativeWindowBase != null)
                    {
                        _scrollBarNativeWindowBase.Dispose();
                        _scrollBarNativeWindowBase = null;
                    }
                    _scrollBar = null;
                }
                ReleaseHandle();
            }
            _disposed = true;
        }

    }
    #endregion
}
